package stendcontrol;

import java.util.ArrayList;

public class MessageBuilder
        implements TransmitListener, Runnable {

    /**
     * количество входных буферов
     */
    protected final int cnt_bufbuilder = 2;
    /**
     * длина буферов
     */
    protected final int len_bufbuilder = 1000;
    /**
     * количество измерений в последовательности измерений
     */
    protected final int len_measure_sequence =
            Configurator.MAX_SIZE_MEASURE_SEQUENCE;
    /**
     * список слушателей пакетов
     */
    protected ArrayList<MessageListener> msg_lstrs = new ArrayList<>();
    /**
     * массив буферов
     */
    protected BufferBuilder[] buffer_builder =
            new BufferBuilder[cnt_bufbuilder];
    protected BufferBuilder buf_measure_sequence =
            new BufferBuilder(len_measure_sequence, Configurator.LONG_LEN_MSG);
    /**
     * номер текущего бефера
     */
    protected int cur_buffer = 0;
    /**
     * номер бефера на обработку
     */
    protected int process_buffer = 0;
    /**
     * метка заполнения текущего буфера
     */
    protected int cnt_in_curbuf = 0;
    /**
     * метка заполнения буфера последоватедьности
     */
    protected int cnt_in_measbuf = 0;
    /**
     * Последняя сгенерированная последовательность измерений
     */
    protected MeasureSeqMessage lastMeasureSeq;

    public MessageBuilder() {
        for (int i = 0; i < cnt_bufbuilder; i++) {
            buffer_builder[i] = new BufferBuilder(len_bufbuilder,
                    Configurator.LONG_LEN_MSG);
        }
    }

    /**
     * Operation
     *
     * @param msgListener
     * @return
     */
    public void addMessageListener(MessageListener msgListener) {
        msg_lstrs.add(msgListener);
    }

    /**
     * Operation
     *
     * @param msgListener
     * @return
     */
    public void removeMessageListener(MessageListener msgListener) {
        msg_lstrs.remove(msgListener);
    }

    /**
     * Operation
     *
     * @param n_msg_lstr
     * @return
     */
    public void removeMessageListener(int n_msg_lstr) {
        msg_lstrs.remove(n_msg_lstr);
    }

    /**
     * Удалить всех слушателей сообщений
     */
    public void removeAllMessageListener() {
        msg_lstrs.clear();
    }

    /**
     * Operation
     *
     * @return long
     */
    public long getCountLostPackets() {
        return 0;
    }

    /**
     * Operation
     *
     * @return long
     */
    public long getCountPackets() {
        return 0;
    }

    @Override
    public void transmitPerformed(byte[] buf, int len) {
        if (cnt_in_curbuf < len_bufbuilder) {
            buffer_builder[cur_buffer].add(buf, len);
            cnt_in_curbuf = buffer_builder[cur_buffer].getSize();
        } else {
            incCurBuffer();
            transmitPerformed(buf, len);
        }
    }

    private synchronized void incCurBuffer() {
        if (++cur_buffer >= cnt_bufbuilder) {
            cur_buffer = 0;
        }
        if (cur_buffer == process_buffer) {
            StendLogger.log(StendLogger.MSG_ERROR, "Fatal error! "
                    + "all buffers in MessageBuilder overflow! "
                    + "Measures is losted! Need many buffers.");
        }
        buffer_builder[cur_buffer].removeAll();
        cnt_in_curbuf = 0;
        notify();
    }
    protected final int MSG_MEASURE = 1;
    protected final int MSG_STATUS = 2;

    /**
     * Обработка заполненного буфера
     */
    private synchronized void processingBuffer() {
        while (cur_buffer == process_buffer && building) {
            try {
                wait();
            } catch (InterruptedException ex) {
                StendLogger.log(StendLogger.MSG_ERROR, "Error processed full"
                        + " bufer in MessageBuilder msg: " + ex.getMessage());
            }
        }
        byte[] lbuf;
        int lbuf_type;
        int cnt_measures = buffer_builder[process_buffer].getSize();
        for (int i = 0; i < cnt_measures; i++) {
            lbuf = buffer_builder[process_buffer].get(i);
            if (lbuf.length != 0) {
                lbuf_type = lbuf[0] >> 4;
                if (lbuf_type == MSG_MEASURE
                        && lbuf.length == Configurator.LONG_LEN_MSG) {
                    buf_measure_sequence.add(lbuf, lbuf.length);
                    if (buf_measure_sequence.getSize() == len_measure_sequence
                            || (!building && i == cnt_measures - 1)) {
                        transtalionToListenes(genMeasureSeqFromBuffer(
                                buf_measure_sequence));
                        buf_measure_sequence.removeAll();
                        cnt_measures = 0;
                    }
                } else if (lbuf_type == MSG_STATUS
                        && lbuf.length == Configurator.SHORT_LEN_MSG) {
                    if (buf_measure_sequence.getSize() > 0) {
                        transtalionToListenes(genMeasureSeqFromBuffer(
                                buf_measure_sequence));
                        buf_measure_sequence.removeAll();
                        cnt_measures = 0;
                    }
                    transtalionToListenes(genStatusMessage(lbuf));
                } else {
                    StendLogger.log(StendLogger.MSG_WARNING, "Unknow packet"
                            + "processing buffer");
                }
            } else {
                StendLogger.log(StendLogger.MSG_WARNING, "Empty packet in"
                        + "processing buffer");
            }
        }
        if (++process_buffer == cnt_bufbuilder) {
            process_buffer = 0;
        }
    }

    private StatusMessage genStatusMessage(byte[] buf) {
        if (buf.length >= 3) {
            return new StatusMessage(buf[1], buf[2]);
        } else {
            StendLogger.log(StendLogger.MSG_ERROR, "Small buffer for "
                    + "get StatusMessage. Length buf " + buf.length
                    + " min need length: 3");
            return null;
        }
    }

    private MeasureSeqMessage genMeasureSeqFromBuffer(BufferBuilder bbild) {
        MeasureSeqMessage msm = new MeasureSeqMessage(lastMeasureSeq);
        if (lastMeasureSeq != null) {
            lastMeasureSeq.setNextMeasureSeqMessage(msm);
        }
        lastMeasureSeq = msm;

        int[] sensors = new int[Configurator.COUNT_SENSOR];
        for (int i = 0; i < bbild.getSize(); i++) {
            for (int j = 0; j < sensors.length; j++) {
                sensors[j] = getSensor(bbild.get(i), j);
            }
            msm.addMeasure(sensors, getPosition(bbild.get(i)));
        }
        bbild.removeAll();
        return msm;
    }

    private int getSensor(byte[] buf, int nsensor) {
        if (nsensor < Configurator.COUNT_SENSOR) {
            if (buf.length >= (1 + nsensor * 2)) {
                return (buf[nsensor * 2 + 1] << 8) | (buf[nsensor * 2 + 2]);
            } else {
                StendLogger.log(StendLogger.MSG_WARNING, "Small buffer for get "
                        + "sensor. Length buf " + buf.length
                        + " min need length: " + (1 + nsensor * 2)
                        + " N sensor: " + nsensor);
                return -1;
            }
        } else {
            StendLogger.log(StendLogger.MSG_WARNING, "Requested bad number "
                    + "sensor: " + nsensor
                    + " max sensor: " + (Configurator.COUNT_SENSOR - 1));
            return -1;
        }
    }

    private long getPosition(byte[] buf) {
        if (buf.length >= 5) {
            return (buf[buf.length - 4] << 24) | (buf[buf.length - 3] << 16)
                    | (buf[buf.length - 2] << 8) | (buf[buf.length - 1]);
        } else {
            StendLogger.log(StendLogger.MSG_WARNING, "Small buffer for get "
                    + "position. Length buf " + buf.length
                    + " min need length: 5");
            return -1;
        }
    }

    private void transtalionToListenes(AbstractMessage amess) {
        int i = msg_lstrs.size();
        while (i-- != 0) {
            msg_lstrs.get(i).msgBuilded(amess);
        }
    }
    private boolean building = true;

    public synchronized void stopBuilding() {
        building = false;
        notify();
    }

    @Override
    public void run() {
        building = true;
        while (building) {
            processingBuffer();
        }
    }
}
